home *** CD-ROM | disk | FTP | other *** search
/ Nebula 1 / Nebula One.iso / Internet / News / Alexandra.0.82 / Source / NewsgroupSet.m < prev    next >
Encoding:
Text File  |  1996-01-30  |  22.8 KB  |  942 lines

  1. #include <stdio.h>
  2. #include <assert.h>
  3. #include <math.h>
  4. #import "Alexandra.h"
  5. #import "NewsgroupSet.h"
  6. #import "readline.h"
  7. #import "Newsgroup.h"
  8. #import "ArticleSetMatrix.h"
  9. #import "NNTP.h"
  10. #import "ArticleSet.h"
  11. #import "ArticleViewControl.h"
  12. #import <misckit/MiscSortedList.h>
  13. #import "descriptors.h"
  14. #import "response_codes.h"
  15. #import "NewsGroupSetBrowser.h"
  16. #import <misckit/MiscAppDefaults.h>
  17. #import "MatrixScroller.h"
  18.  
  19. @implementation NewsgroupSet
  20.  
  21.  
  22. - init
  23. {
  24.    [super init];
  25.    myMList=[[MiscSortedList alloc] init];
  26.    [myMList setSortEnabled:FALSE];
  27.    [myMList setSortOrder:Misc_ASCENDING];
  28.    [ERROR_MANAGER addObserver:self 
  29.                selector:@selector(dumpNewsrc:) forError:S15];
  30.    [ERROR_MANAGER addObserver:self 
  31.                selector:@selector(updateNewsgroups) 
  32.                 forError:ENOTEPrefsChanged2];
  33.                     
  34.    unselAction=@selector(clearMatrix);
  35.    return self;
  36. }
  37.  
  38. - free
  39. {
  40.    [myMList makeObjectsPerform:@selector(free)];
  41.    [myMList free];
  42.    [ERROR_MANAGER removeObserver:self forError:S15];
  43.    [ERROR_MANAGER removeObserver:self forError:ENOTEPrefsChanged2];
  44.     
  45.    return [super free];
  46. }
  47.  
  48. - scanNewsrcAndActive
  49. {
  50.    char aGroup[255];
  51.    int i,j;
  52.    FILE *newsrc;
  53.    Newsgroup *aNewsgroup;
  54.    HashTable *positionInList;
  55.    const char *home;
  56.    const char *nntp_server;
  57.    char *filename;
  58.    char *buffer;
  59.    int position;
  60.    BOOL exists;
  61.    Storage *array;
  62.    newsgroupDesc *ngDesc;
  63.    int statusCode;
  64.  
  65.    
  66.    // compose name of newsrc file
  67.    home=NXHomeDirectory();
  68.    nntp_server=[nntpServer serverName];
  69.    filename=(char *)malloc((strlen(home)+strlen(nntp_server)+12)*sizeof(char));
  70.    strcpy(filename,home);
  71.    strcat(filename,"/.");
  72.    strcat(filename,nntp_server);
  73.    strcat(filename,".newsrc");
  74.  
  75.    // open newsrc file, parse all lines and make newsgroup list
  76.    exists=FALSE;
  77.    newsrc=fopen(filename,"r");
  78.    if(newsrc!=NULL){
  79.      exists=TRUE;
  80.      while(!feof(newsrc)){
  81.         readline(newsrc);
  82.         buffer=get_buffer();
  83.         i=0;
  84.         while(buffer[i]!='\0' && buffer[i]!=':' && buffer[i]!='!'){
  85.          i++;
  86.         }
  87.         if(buffer[i]!='\0'){
  88.            strncpy(aGroup,buffer,i);
  89.            aGroup[i]='\0';
  90.            aNewsgroup=[[[[Newsgroup alloc] initTextCell:aGroup] setBogus:TRUE] unsetTag];
  91.            if((buffer[i+1]!='\0')&&(buffer[i+2]!='\0'))
  92.               [aNewsgroup setReadList:(buffer+i+2)];
  93.            else
  94.               [aNewsgroup setReadList:""];
  95.            if(buffer[i]==':')
  96.               [aNewsgroup setSubscribed];
  97.            [myMList addObject:aNewsgroup];
  98.         }
  99.      }           
  100.    }
  101.    fclose(newsrc);
  102.    free(filename);
  103.  
  104.    //put every newsgroupname in a hashtable
  105.    j=[myMList count];
  106.    positionInList=[[HashTable alloc] initKeyDesc:"*" valueDesc:"i" capacity:(int)floor(j*1.3)];
  107.    for(i=0;i<j;i++)
  108.       [positionInList insertKey:[[myMList objectAt:i] stringValue] value:(void *)i+1];
  109.  
  110.    //ISSUE LIST COMMAND
  111.    //Slow link feature means don't do the "list" command
  112.    if(([nntpServer slowLink])&&([nntpServer timeTag])&&(exists)){
  113.       j=[myMList count];
  114.       for(i=0;i<j;i++){
  115.          aNewsgroup=[myMList objectAt:i];
  116.          [[[aNewsgroup unsetTag] setBogus:FALSE] setPostable:'y'];
  117.          if([aNewsgroup isSubscribed]==TRUE)
  118.             if((statusCode=[nntpServer requestGroup:aNewsgroup])!=OK_GROUP){ //get min+max article
  119.                 [aNewsgroup setBogus:TRUE];
  120.             }
  121.       }
  122.       
  123.       //see if any new groups
  124.       array=[[Storage alloc] initCount:0 elementSize:sizeof(newsgroupDesc) description:"{*llc}"];
  125.       [nntpServer scanNewGroups:array];
  126.       j=[array count];
  127.       if(j>0) //new groups?
  128.          for(i=0;i<j;i++){
  129.             BOOL found;
  130.             newsgroupDesc *aNGDesc=(newsgroupDesc *)[array elementAt:(unsigned)i];
  131.             if([self lookupGroup:aNGDesc->groupname found:&found],found==FALSE){
  132.                aNewsgroup=[[Newsgroup alloc] initTextCell:aNGDesc->groupname];
  133.                [[[[[aNewsgroup setBogus:FALSE] setMin:aNGDesc->min] setMax:aNGDesc->max] setPostable:aNGDesc->post] setTag];  //set tag-> new group
  134.                [myMList addObject:aNewsgroup];
  135.             }
  136.          }
  137.       for(i=0;i<j;i++)
  138.          free(((newsgroupDesc *)[array elementAt:(unsigned)i])->groupname);
  139.       [array free];
  140.    }
  141.    else{ //NO SLOW LINK OR FIRST TIME PROGRAM WAS STARTED (NO TIMETAG)
  142.       //get active newsgroups from nntp server
  143.       array=[[Storage alloc] initCount:0 elementSize:sizeof(newsgroupDesc) description:"{*llc}"];
  144.       [nntpServer scanActive:array];
  145.       // parse active newsgroups
  146.       j=[array count];
  147.       for(i=0;i<j;i++){
  148.          ngDesc=(newsgroupDesc *)[array elementAt:(unsigned)i];   
  149.          position=(int)[positionInList valueForKey:ngDesc->groupname];
  150.          //new group?
  151.          if(position==0){
  152.             aNewsgroup=[[[Newsgroup alloc] initTextCell:ngDesc->groupname] setTag];
  153.             [myMList addObject:aNewsgroup];
  154.            }
  155.          else
  156.             aNewsgroup=[myMList objectAt:(position-1)]; // subtract one of position
  157.          [[[[aNewsgroup setBogus:FALSE] setMin:ngDesc->min] setMax:ngDesc->max] setPostable:ngDesc->post];
  158.          free(ngDesc->groupname);
  159.       }
  160.       [array free];
  161.    }
  162.  
  163.    [positionInList free];
  164.  
  165.    //remove bogus and unvalid Newsgroups
  166.    for(i=[myMList count]-1;i>=0;i--){
  167.         id aNewsgroup=[myMList objectAt:i];
  168.       if([aNewsgroup bogus] || strchr([aNewsgroup stringValue],':'))
  169.          [[myMList removeObjectAt:i] free];
  170.     }
  171.  
  172.    //approximate the number of unread articles in every group
  173.    j=[myMList count];
  174.    for(i=0;i<j;i++)
  175.       [[myMList objectAt:i] approxNumUnread];
  176.  
  177.    //check for new newsgroups in newsgroup list (new ng <-> ng is taged)
  178.    exists=FALSE;
  179.    j=[myMList count];
  180.    for(i=0;i<j;i++)
  181.       if([[myMList objectAt:i] isTaged]==TRUE){
  182.          exists=TRUE;
  183.          break;
  184.       }
  185.  
  186.    if(exists==TRUE)
  187.       [myMatrix setButtonTitle:"New Newsgroups"]; //some new groups
  188.    else{
  189.       [myMatrix setButtonTitle:"Subscribed Newsgroups"]; //no new groups
  190.       j=[myMList count];
  191.       for(i=0;i<j;i++){
  192.          aNewsgroup=[myMList objectAt:i];
  193.          // setTag <-> newsgroup is visible in matrix <-> newsgroup is subscribed
  194.          if([aNewsgroup isSubscribed]==TRUE)
  195.             [aNewsgroup setTag];
  196.          else
  197.             [aNewsgroup unsetTag];
  198.       }
  199.    }
  200.  
  201.    return self;
  202. }
  203.        
  204. - newGroups:sender
  205. {
  206.    Storage *array;
  207.    int i,j;
  208.    newsgroupDesc *aNGDesc;
  209.    Newsgroup *aNewsgroup;
  210.    BOOL found;
  211.  
  212.    array=[[Storage alloc] initCount:0 elementSize:sizeof(newsgroupDesc) description:"{*llc}"];
  213.  
  214.    [nntpServer scanNewGroups:array];
  215.  
  216.    j=[array count];
  217.    if(j==0){
  218.       NXBeep();
  219.       [myMatrix updateButtonTitle];
  220.    }
  221.    else{
  222.       [myMList makeObjectsPerform:@selector(unsetTag)];
  223.       [myMatrix setButtonTitle:"New Newsgroups"];
  224.       for(i=0;i<j;i++){
  225.          aNGDesc=(newsgroupDesc *)[array elementAt:(unsigned)i];
  226.          if([self lookupGroup:aNGDesc->groupname found:&found],found==FALSE){
  227.             aNewsgroup=[[Newsgroup alloc] initTextCell:aNGDesc->groupname];
  228.             [[[[aNewsgroup setBogus:FALSE] setMin:aNGDesc->min] setMax:aNGDesc->max] setPostable:aNGDesc->post];
  229.             [[aNewsgroup approxNumUnread] setTag];
  230.             [myMList addObject:aNewsgroup];
  231.          }
  232.       }
  233.       [[myMatrix reloadMatrix] display];
  234.       [self sync];
  235.    }
  236.  
  237.    j=[array count];
  238.    for(i=0;i<j;i++)
  239.       free(((newsgroupDesc *)[array elementAt:(unsigned)i])->groupname);
  240.    [array free];
  241.  
  242.    return self;
  243. }
  244.  
  245. -newNews:sender
  246. {
  247.    int i,j;
  248.    int statusCode;
  249.    BOOL slowLink;
  250.    id oldSelection;
  251.    BOOL oldSelNewNews=FALSE;
  252.    BOOL cellsRemoved=FALSE;
  253.     
  254.    j=[myMList count];
  255.    if(j==0)
  256.       return self;
  257.    oldSelection=currentSelection;
  258.  
  259.    slowLink=[nntpServer slowLink];
  260.    for(i=0;i<j;i++){
  261.       Newsgroup *aGroup=[myMList objectAt:i];
  262.       if(aGroup==nil) continue;
  263.       if(([aGroup isSubscribed]==TRUE)||(aGroup==currentSelection)){
  264.          long oldMax=[aGroup maxNumber];
  265.          statusCode=[nntpServer requestGroup:aGroup];
  266.          if(statusCode!=OK_GROUP){
  267.             [myMatrix removeInvalidCell:aGroup andUpdate:FALSE];
  268.             i--; //list length gets smaller
  269.             j--;
  270.             if(oldSelection==aGroup)
  271.                oldSelection=nil;
  272.             cellsRemoved=TRUE;
  273.             }
  274.          else{
  275.             //BOOL setBack=TRUE;
  276.             long newMax=[aGroup maxNumber];
  277.             long diff=newMax-oldMax;
  278.             if(diff>0){
  279.                if(aGroup==oldSelection)
  280.                   oldSelNewNews=TRUE;
  281.                if((slowLink==TRUE)&&([aGroup isDelayed]==TRUE)){
  282.                   [aGroup approxNumUnread];
  283.                   //setBack=FALSE;
  284.                }
  285.                else
  286.                   [aGroup incNumberUnreadArticles:(int)diff];
  287.             }
  288.          }
  289.       }
  290.    }
  291.    if(cellsRemoved==TRUE)
  292.       [myMatrix update];
  293.  
  294.     if(!strcmp([[myMatrix selectionButton] title],"Groups with Articles"))
  295.         [self displayGroupsWithUnreadNews:self];
  296.  
  297.    if(currentSelection!=nil){
  298.       statusCode=[nntpServer requestGroup:currentSelection];
  299.       if(statusCode!=OK_GROUP){
  300.          [myMatrix removeInvalidCell:currentSelection];
  301.          return self;
  302.       }
  303.       if((oldSelNewNews==TRUE) && (currentSelection==oldSelection)){
  304.          id artMatrix=[theArticleSet theMatrix];
  305.          [currentSelection scanArticles:nntpServer visibleIn:artMatrix];
  306.          [artMatrix reloadMatrix];
  307.          [[[artMatrix display] window] flushWindow];
  308.          [theArticleSet sync];
  309.       }
  310.       if(currentSelection!=oldSelection){
  311.          currentSelection=nil;
  312.          [self selectNewsgroup:self];
  313.       }
  314.    }
  315.    if(cellsRemoved==FALSE)
  316.       [myMatrix display];
  317.    
  318.    return self;
  319. }
  320.  
  321. - updateNewsgroups
  322. {
  323.    int i,j=[myMList count];
  324.     for(i=0;i<j;i++)
  325.         [[myMList objectAt:i] updateArticles];
  326.  
  327.     [theArticleSet redisplayMatrix];        
  328.     return self;
  329. }
  330. - dumpNewsrc:sender
  331. {
  332.    int i,j;
  333.    Newsgroup *aNewsgroup;
  334.    char *filename,*oldfile;
  335.    const char *home;
  336.    const char *nntp_server;
  337.    NXStream *nrcStream;
  338.    const char *aString;
  339.    char aChar;
  340.  
  341.    if([myMList count]==0)
  342.       return self;
  343.  
  344.    nrcStream=NXOpenMemory(NULL,0,NX_READWRITE);
  345.  
  346.    j=[myMList count];
  347.    for(i=0;i<j;i++){
  348.       aNewsgroup=[myMList objectAt:i];
  349.       aString=[aNewsgroup stringValue];
  350.       NX_ASSERT([aNewsgroup stringValue]!=NULL,"Newsgroup without name");
  351.       NXWrite(nrcStream,aString,strlen(aString));
  352.       if([aNewsgroup isSubscribed]==TRUE)
  353.          NXWrite(nrcStream,": ",2);
  354.       else
  355.          NXWrite(nrcStream,"! ",2);
  356.       [aNewsgroup dumpReadList:nrcStream];
  357.       NXSeek(nrcStream,(-1)*sizeof(char),NX_FROMCURRENT);
  358.       if(NXRead(nrcStream,&aChar,sizeof(char)),aChar==' ')
  359.          NXSeek(nrcStream,(-1)*sizeof(char),NX_FROMCURRENT);
  360.       NXWrite(nrcStream,"\n",1);
  361.       }
  362.     
  363.     //rename + save new
  364.     home=NXHomeDirectory();
  365.    i=strlen(home);
  366.  
  367.    nntp_server=[nntpServer serverName];
  368.    j=strlen(nntp_server);
  369.  
  370.    filename=(char *)calloc(i+j+12,sizeof(char));
  371.     sprintf(filename,"%s/.%s.newsrc",home,nntp_server);
  372.  
  373.    oldfile=(char *)malloc((strlen(filename)+2)*sizeof(char));
  374.    strcpy(oldfile,filename);
  375.    strcat(oldfile,"~");
  376.    rename(filename,oldfile);
  377.    free(oldfile);
  378.  
  379.    NXSaveToFile(nrcStream,filename);
  380.    NXCloseMemory(nrcStream,NX_FREEBUFFER);
  381.    free(filename);
  382.     
  383.    return self;
  384. }
  385.  
  386. - displayAllGroups:sender
  387. {
  388.    int i,j;
  389.  
  390.    j=[myMList count];
  391.    for(i=0;i<j;i++)
  392.       [[myMList objectAt:i] setTag];
  393.    [[myMatrix reloadMatrix] display];
  394.  
  395.    [myMatrix setButtonTitle:"All Newsgroups"];
  396.  
  397.    return self;
  398. }
  399.  
  400. - displaySubscribedGroups:sender
  401. {
  402.    int i,j;
  403.    Newsgroup *oldNG;
  404.  
  405.    j=[myMList count];
  406.    for(i=0;i<j;i++){
  407.       Newsgroup* aNewsgroup;
  408.       aNewsgroup=[myMList objectAt:i];
  409.       if([aNewsgroup isSubscribed])
  410.          [aNewsgroup setTag];
  411.       else
  412.          [aNewsgroup unsetTag];
  413.    }
  414.    [[myMatrix reloadMatrix] display];
  415.  
  416.    oldNG=currentSelection;
  417.    [self sync];
  418.    if((numSelCells==1)&&(oldNG!=currentSelection)){
  419.       currentSelection=oldNG;
  420.       [self selectNewsgroup:self];
  421.    }
  422.  
  423.    [myMatrix setButtonTitle:"Subscribed Newsgroups"];
  424.  
  425.    return self;
  426. }
  427.  
  428. - displayGroupsWithUnreadNews:sender
  429. {
  430.    int i,j;
  431.    Newsgroup *oldNG;
  432.  
  433.    j=[myMList count];
  434.    for(i=0;i<j;i++){
  435.       Newsgroup* aNewsgroup;
  436.       aNewsgroup=[myMList objectAt:i];
  437.       if([aNewsgroup isSubscribed] && [aNewsgroup numberUnreadArticles]>0)
  438.          [aNewsgroup setTag];
  439.       else
  440.          [aNewsgroup unsetTag];
  441.    }
  442.     if(currentSelection!=nil)
  443.         [currentSelection setTag];
  444.         
  445.    [[myMatrix reloadMatrix] display];
  446.  
  447.    oldNG=currentSelection;
  448.    [self sync];
  449.    if((numSelCells==1)&&(oldNG!=currentSelection)){
  450.       currentSelection=oldNG;
  451.       [self selectNewsgroup:self];
  452.    }
  453.  
  454.    [myMatrix setButtonTitle:"Groups with Articles"];
  455.  
  456.    return self;
  457. }
  458.  
  459. - selectNewsgroup:sender
  460. {
  461.    Newsgroup* oldNG;
  462.  
  463.    [[myMatrix window] makeFirstResponder:myMatrix];
  464.    oldNG=currentSelection;
  465.    [self sync];
  466.  
  467.    if(oldNG!=currentSelection)
  468.       if(numSelCells==1){
  469.          int statusCode=[nntpServer requestGroup:currentSelection];
  470.          if(statusCode!=OK_GROUP){
  471.             [myMatrix perform:@selector(removeInvalidCell:) with:currentSelection afterDelay:0.0 cancelPrevious:YES];
  472.             return self;
  473.          }
  474.         [currentSelection scanArticles:nntpServer];
  475.  
  476.         [[theArticleSet theMatrix] setButtonTitle:"Unread Articles"];
  477.         [theArticleSet loadGroup:currentSelection];
  478.         [myMatrix display];
  479.           [self dumpNewsrc:self];
  480.       }
  481.       else
  482.          [nntpServer unselectCurrentGroup];
  483.    
  484.    return self;
  485. }
  486.  
  487. - (BOOL)selectNewsgroupNamed:(const char *)gname
  488. {
  489.     BOOL found;
  490.     Newsgroup *aGroup;
  491.     int position;
  492.     
  493.     position=[self lookupGroup:gname found:&found];
  494.     if(!found)
  495.         return FALSE;
  496.  
  497.     aGroup=[myMList objectAt:position];
  498.     if(![aGroup isTaged])
  499.         [self displayAllGroups:self];
  500.         
  501.     return [myMatrix selectNewsgroupNamed:gname];
  502. }
  503.     
  504. - subscribeOrUnsubscribe:(BOOL)flag
  505. {
  506.    /*flag==TRUE <=> subscribe */
  507.    int i,j;
  508.    List *selectionList=nil;
  509.    Newsgroup *aNewsgroup;
  510.  
  511.    selectionList=[myMatrix getCurrSelections];
  512.    j=[selectionList count];
  513.    for(i=0;i<j;i++){
  514.       aNewsgroup=[selectionList objectAt:i];
  515.       if(flag==TRUE)
  516.          [aNewsgroup setSubscribed];
  517.       else
  518.          [aNewsgroup setUnsubscribed];
  519.    }
  520.    [selectionList free];
  521.    [myMatrix display];
  522.    return self;
  523. }
  524.   
  525. - subscribe:sender
  526. {
  527.    [self subscribeOrUnsubscribe:TRUE];
  528.    return self;
  529. }
  530.  
  531. - unsubscribe:sender
  532. {
  533.    [self subscribeOrUnsubscribe:FALSE];
  534.    return self;
  535. }
  536.  
  537. - markAllRead:sender
  538. {
  539.    int i,j;
  540.    id ng=[myMatrix getCurrSelections];
  541.  
  542.    j=[ng count];
  543.    if(j==0){
  544.       NXRunAlertPanel("ALEXANDRA","No newsgroup(s) selected.",NULL,NULL,NULL);
  545.       return self;
  546.    }
  547.     else if(j>1){
  548.         int r=NXRunAlertPanel("ALEXANDRA",
  549.                         "Catchup %d selected groups?",NULL,"Cancel",NULL,j);
  550.         if(r==NX_ALERTALTERNATE)
  551.             return self;
  552.     }
  553.     
  554.    for(i=0;i<j;i++)
  555.       [[ng objectAt:i] markAllReadUntil:nil];
  556.  
  557.    if(j==1)
  558.       [theArticleSet redisplayMatrix];
  559.    [myMatrix display];
  560.  
  561.    return self;
  562. }
  563.  
  564. - markAllUntilSelRead:sender;
  565. {
  566.    id ng=[myMatrix getCurrSelections];
  567.  
  568.    if([ng count]!=1)
  569.       return self;
  570.  
  571.    [[ng objectAt:0] markAllReadUntil:[theArticleSet currentSelection]];
  572.    [theArticleSet redisplayMatrix];
  573.    [myMatrix display];
  574.  
  575.    return self;
  576. }
  577.  
  578. - markAllClever:sender;
  579. {
  580.     id selectedArticle;
  581.     static time_t lastCatchup=0;    
  582.  
  583.     if(!currentSelection)
  584.         return [self markAllRead:self];
  585.         
  586.     selectedArticle=[theArticleSet currentSelection];
  587.     if(!selectedArticle)
  588.         [self markAllRead:sender];
  589.     else{
  590.         time_t now=time(NULL);
  591.         if(now>lastCatchup+3)
  592.             [self markAllUntilSelRead:self];
  593.         else
  594.             [self markAllRead:sender];
  595.         lastCatchup=now;
  596.     }
  597.     
  598.     return self;
  599. }
  600.  
  601. - (int)lookupGroup:(const char *)aGroup found:(BOOL *)f
  602. {
  603.    int cmpResult,j;
  604.    Newsgroup *aNewsgroup;
  605.  
  606.    for(j=[myMList count]-1;j>=0;j--){
  607.       aNewsgroup=[myMList objectAt:j];
  608.       cmpResult=strcmp([aNewsgroup stringValue],aGroup);
  609.       if(cmpResult==0){
  610.          *f=TRUE;
  611.          return j;
  612.       }
  613.    }
  614.    *f=FALSE;
  615.    return(0);
  616. }
  617.  
  618. - shuffleList:(id)sourceCell to:(id)destCell
  619. {
  620.    unsigned int from,to;
  621.  
  622.    from=[myMList indexOf:sourceCell];
  623.    to=[myMList indexOf:destCell];
  624.  
  625.    NX_ASSERT(from!=NX_NOT_IN_LIST,"Internal DS mismatch");
  626.    NX_ASSERT(to!=NX_NOT_IN_LIST,"Internal DS mismatch");
  627.  
  628.    [myMList removeObjectAt:from];
  629.    [myMList insertObject:sourceCell at:to];
  630.   
  631.    return self;
  632. }
  633.  
  634. - upOrDown:(int)delta
  635. {
  636.    while([[theArticleSet theMatrix] selectNextCell:delta]==FALSE)
  637.       if([myMatrix selectNextCell:delta]==FALSE){
  638.          NXRunAlertPanel("ALEXANDRA","No more articles.",NULL,NULL,NULL);
  639.          break;
  640.       }
  641.    return self;
  642. }
  643.  
  644. - upOrDownOnePage:(int)delta
  645. {
  646.    NXRect wholeBox;
  647.    NXRect visibleBox;
  648.    id theText=[theArticleViewControl theText];
  649.  
  650.    [theText getVisibleRect:&visibleBox];
  651.    [theText getFrame:&wholeBox];
  652.  
  653.    if(((delta==-1) && NX_Y(&visibleBox)<=3.0) || 
  654.       ((delta==1) && (NX_HEIGHT(&wholeBox) - NX_Y(&visibleBox) - NX_HEIGHT(&visibleBox))<=3.0))
  655.       [self upOrDown:delta];
  656.    else{
  657.       NX_Y(&visibleBox)+=delta*(NX_HEIGHT(&visibleBox)*0.75); 
  658.       [theText scrollRectToVisible:&visibleBox];
  659.    }
  660.  
  661.    return self;
  662. }
  663.  
  664. - up:sender
  665. {
  666.    [self upOrDown:-1];
  667.    return self;
  668. }
  669.  
  670. - down:sender
  671. {
  672.    [self upOrDown:1];
  673.    return self;
  674. }
  675.  
  676. - upOnePage:sender
  677. {
  678.    [self upOrDownOnePage:-1];
  679.    return self;
  680. }
  681.  
  682. - downOnePage:sender
  683. {
  684.    [self upOrDownOnePage:1];
  685.    return self;
  686. }
  687.  
  688. - sortNewsgroupList:sender
  689. {
  690.     [myMList unsort];
  691.    [myMList sort];
  692.    [myMatrix reloadMatrix];
  693.    [myMatrix display];
  694.  
  695.    return self;
  696. }
  697.  
  698. - switchToView:view
  699. {
  700.    NXRect frame;
  701.    id sview;
  702.    id newOther,newMatrix;
  703.    char *defaultViewerStr;
  704.    const char *nntpname;
  705.  
  706.    if([myMatrix isKindOf:[NewsGroupSetBrowser class]]){
  707.       newOther=myMatrix;
  708.       newMatrix=[otherView docView];
  709.    }
  710.    else{
  711.       newOther=[[myMatrix superview] superview];
  712.       newMatrix=otherView;
  713.    }
  714.    sview=[newOther superview];
  715.    [newOther getFrame:&frame];
  716.  
  717.    [newOther removeFromSuperview];
  718.    [sview addSubview:otherView];
  719.    [otherView setFrame:&frame];
  720.    myMatrix=newMatrix;
  721.    [myMatrix loadMatrix];
  722.  
  723.    if(currentSelection!=nil)
  724.       if([myMatrix isKindOf:[NewsGroupSetBrowser class]]){
  725.          const char *selName=[currentSelection stringValue];
  726.          char *path=(char *)malloc((strlen(selName)+2)*sizeof(char));
  727.          sprintf(path,".%s",selName);
  728.          [[myMatrix window] disableFlushWindow];
  729.          [myMatrix setAutodisplay:NO];
  730.          [myMatrix setPath:path];
  731.          [[myMatrix setAutodisplay:YES] display];
  732.          [[[myMatrix window] reenableFlushWindow] flushWindow];
  733.          free(path);
  734.       }
  735.       else{
  736.          [otherView display];
  737.          [myMatrix selectCell:currentSelection];
  738.       }
  739.    else
  740.       [otherView display]; 
  741.    otherView=newOther;
  742.   
  743.    nntpname=[nntpServer serverName];
  744.    defaultViewerStr=(char *)malloc((strlen(nntpname)+21)*sizeof(char));
  745.    sprintf(defaultViewerStr,"BrowserViewEnabled %s",nntpname);
  746.    if([otherView isKindOf:[NewsGroupSetBrowser class]])
  747.       [NXApp setDefault:defaultViewerStr toBool:NO];
  748.    else
  749.       [NXApp setDefault:defaultViewerStr toBool:YES];
  750.    free(defaultViewerStr);
  751.     
  752.    return self;
  753. }
  754.  
  755. - switchToListView:sender
  756. {
  757.    if([myMatrix isKindOf:[NewsGroupSetBrowser class]]){
  758.       id aCell;
  759.       [self switchToView:otherView];
  760.       if((aCell=[myMatrix selectedCell])!=nil)
  761.          [myMatrix scrollCellToVisible:[[myMatrix cellList] indexOf:aCell]
  762.                    upperOffset:1.5 lowerOffset:1.5];
  763.    }
  764.    return self;
  765. }
  766.  
  767. - switchToBrowserView:sender
  768. {
  769.    if(![myMatrix isKindOf:[NewsGroupSetBrowser class]]){
  770.       [self switchToView:otherView];
  771.       [myMatrix scrollSelectionToVisible];
  772.    }
  773.    return self;
  774. }
  775.  
  776.  
  777.  
  778. - (BOOL)selectViewCellEnabled:menuCell
  779. {
  780.     if([menuCell action]==@selector(switchToListView:) && 
  781.       ([myMatrix isKindOf:[NewsGroupSetBrowser class]]))
  782.          return TRUE;
  783.     if([menuCell action]==@selector(switchToBrowserView:) &&
  784.       (![myMatrix isKindOf:[NewsGroupSetBrowser class]]))
  785.          return TRUE;
  786.    return FALSE;
  787. }
  788.  
  789.  
  790. - (BOOL)selectURL:(const char *)urlstring
  791. {
  792.     BOOL success=FALSE;
  793.     BOOL apanelWasRunning=FALSE;
  794.     
  795.     char *groupname=NULL;
  796.     long seqnumber=0;
  797.     char *msgid=NULL;
  798.     BOOL isNewsURL=FALSE;
  799.     char *buf1,*buf2,*buf3;
  800.     
  801.     if(!urlstring)
  802.         return FALSE;
  803.         
  804.     buf1=NXCopyStringBuffer(urlstring);
  805.     
  806.     // nntp or news url?
  807.     buf2=strstr(buf1,"news:");
  808.     if(buf2){
  809.         success=TRUE;
  810.         isNewsURL=TRUE;
  811.         buf2+=5;
  812.     }
  813.     else{
  814.         buf2=strstr(buf1,"nntp:");
  815.         if(buf2){
  816.             success=TRUE;
  817.             isNewsURL=FALSE;
  818.             buf2+=5;
  819.         }
  820.     }
  821.     
  822.     if(!success){
  823.         NXRunAlertPanel("Service","No valid URL.",NULL,NULL,NULL);
  824.         apanelWasRunning=TRUE;
  825.     }
  826.     
  827.     if(success){
  828.     
  829.         [[myMatrix window] makeKeyAndOrderFront:self];
  830.             
  831.         //skip any / or < chars
  832.         while(*buf2=='/' || *buf2=='<')
  833.             buf2++;
  834.             
  835.         //get useful string
  836.         buf3=strpbrk(buf2,"\t >\r\n");
  837.         if(buf3)
  838.             *buf3='\0';
  839.             
  840.         if(isNewsURL){
  841.             if(strchr(buf2,'@')){ // message-id news url
  842.                 char **groups;
  843.                 
  844.                 // first compose real messageid
  845.                 msgid=malloc((strlen(buf2)+4)*sizeof(char));
  846.                 strcpy(msgid,"<");
  847.                 strcat(msgid,buf2);
  848.                 strcat(msgid,">");
  849.                 
  850.                 // get all groupnames
  851.                 success=[nntpServer findArticle:msgid inGroups:&groups];
  852.                 if(!success){
  853.                     NXRunAlertPanel("Service","Article not available.",NULL,NULL,NULL);
  854.                     apanelWasRunning=TRUE;
  855.                 }
  856.                 
  857.                 //find all group objects
  858.                 if(success){
  859.                     char **gp=groups;
  860.                     MiscSortedList *groupList=[[MiscSortedList alloc] init];
  861.                     [groupList setSortEnabled:NO];
  862.                     [groupList setSortOrder:Misc_ASCENDING];
  863.                     while(*gp){
  864.                         BOOL found=FALSE;
  865.                         int pos=[self lookupGroup:*gp found:&found];
  866.                         if(found)
  867.                             [groupList addObject:[myMList objectAt:pos]];
  868.                         free(*gp);
  869.                         gp++;
  870.                     }
  871.                     
  872.                     // find best group
  873.                     if([groupList count]==0){
  874.                         success=FALSE;
  875.                         NXRunAlertPanel("Service","There's no such article available.",NULL,NULL,NULL);
  876.                         apanelWasRunning=TRUE;
  877.                     }
  878.                     else{
  879.                         int oldStype=[Newsgroup sortType];
  880.                         [Newsgroup setSortType:SORT_BY_SELECT_PRIORITY];
  881.                         [groupList sort];
  882.                         [Newsgroup setSortType:oldStype];
  883.                         groupname=NXCopyStringBuffer([[groupList objectAt:0] stringValue]);
  884.                     }
  885.                     [groupList free];
  886.                 }
  887.             }            
  888.             else{
  889.                 groupname=NXCopyStringBuffer(buf2);
  890.             }
  891.         }
  892.         else{
  893.             char *t;
  894.             if(t=strchr(buf2,'/')){
  895.                 *t='\0';
  896.                 groupname=NXCopyStringBuffer(buf2);
  897.                 seqnumber=atol(t+1);
  898.             }
  899.             else{
  900.                 success=FALSE;
  901.                 NXRunAlertPanel("Service","No valid URL.",NULL,NULL,NULL);
  902.                 apanelWasRunning=TRUE;
  903.             }    
  904.         }
  905.     }
  906.     
  907.     if(success){
  908.  
  909.         // select group
  910.         success=[self selectNewsgroupNamed:groupname];
  911.         if(!success){
  912.             NXRunAlertPanel("Service","There's no newsgroup named '%s'.",NULL,NULL,NULL,groupname);
  913.             apanelWasRunning=TRUE;
  914.         }
  915.     }
  916.     
  917.     if(success){
  918.  
  919.         // select article
  920.         if(isNewsURL && msgid)
  921.             success=[theArticleSet selectArticleWithMsgid:msgid]; 
  922.         else if(!isNewsURL && seqnumber>0)
  923.             success=[theArticleSet selectArticleWithNumber:seqnumber];
  924.         if(!success){
  925.             NXRunAlertPanel("Service","Could not find article.",NULL,NULL,NULL);
  926.             apanelWasRunning=TRUE;
  927.         }
  928.     }
  929.     
  930.     if(groupname) free(groupname);
  931.     if(msgid) free(msgid);
  932.     if(buf1) free(buf1);
  933.     
  934.     if(!success && !apanelWasRunning)
  935.         NXRunAlertPanel("Service","Could not perform service.",NULL,NULL,NULL);
  936.         
  937.     return success;
  938. }
  939.  
  940.  
  941. @end
  942.